/*
 * Server.hpp
 *
 *  Created on: 01/lug/2009
 *      Author: Asteroth
 */

#ifndef __ENG_SERVER_HPP__
#define __ENG_SERVER_HPP__

#include <winsock2.h>
#include <winsock.h>
#include <cstdlib>
#include <vector>
#include <memory>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>

#include "NetworkUtils.hpp"

namespace engine {
namespace net {

class TCPConnection {
private:
	boost::asio::io_service *service;
	boost::asio::ip::tcp::socket* socket;
	NetworkMessageQueue messages;

	void receiveHandler(const boost::system::error_code &error, boost::asio::ip::tcp::socket *socket, size_t bytes_transferred) {
		if (!error) {
			messages.add(NetworkMessage::create(data, bytes_transferred, socket));
			socket->async_receive(boost::asio::buffer(data, length), boost::bind(&TCPServer::receiveHandler, this, boost::asio::placeholders::error, socket, boost::asio::placeholders::bytes_transferred));
		} else {
			for (unsigned int i = 0; i < sockets.size(); i++)
				if (sockets[i] == socket) {
					delete sockets[i];
					sockets[i] = NULL;
					break;
				}
		}
	}

public:
	TCPConnection() {
		service = new boost::asio::io_service();
		socket = boost::asio::ip::tcp::socket(*service);
	}



};

class TCPServer {
private:
	boost::asio::io_service *service;
	boost::asio::ip::tcp::acceptor *acceptor;
	boost::asio::ip::tcp::socket* newSocket;
	std::vector<boost::asio::ip::tcp::socket*> sockets;
	boost::thread* serviceThread;

	char* data;
	int length;
	NetworkMessageQueue messages;

	void acceptHandler(const boost::system::error_code &error)
	{
		if (isConnected()) {
			if (!error) {
				sockets.push_back(newSocket);
				//onClientConnect(newSocket);
				newSocket->async_receive(boost::asio::buffer(data, length), boost::bind(&TCPServer::receiveHandler, this, boost::asio::placeholders::error, newSocket, boost::asio::placeholders::bytes_transferred));
				newSocket = new boost::asio::ip::tcp::socket(acceptor->io_service());
				acceptor->async_accept(*newSocket, boost::bind(&TCPServer::acceptHandler, this, boost::asio::placeholders::error));
			} else {
				acceptor->async_accept(*newSocket, boost::bind(&TCPServer::acceptHandler, this, boost::asio::placeholders::error));
			}
		}
	}

	void receiveHandler(const boost::system::error_code &error, boost::asio::ip::tcp::socket *socket, size_t bytes_transferred) {
		if (!error) {
			//onReceive(socket, data, bytes_transferred);
			messages.add(NetworkMessage::create(data, bytes_transferred, socket));
			socket->async_receive(boost::asio::buffer(data, length), boost::bind(&TCPServer::receiveHandler, this, boost::asio::placeholders::error, socket, boost::asio::placeholders::bytes_transferred));
		} else {
			//onClientDisconnect(socket);
			for (unsigned int i = 0; i < sockets.size(); i++)
				if (sockets[i] == socket) {
					delete sockets[i];
					sockets[i] = NULL;
					break;
				}
		}
	}

	void sendHandler(const boost::system::error_code &error, boost::asio::ip::tcp::socket *socket, size_t bytes_transferred) {
		if (error) {
			for (unsigned int i = 0; i < sockets.size(); i++)
				if (sockets[i] == socket) {
					delete sockets[i];
					sockets[i] = NULL;
					break;
				}
		}
	}

	void runHandler() {
		while (true) {
			try {
				std::cout <<"<try interrupt>.\n";
				boost::this_thread::interruption_point();
			} catch (boost::thread_interrupted &ex) {
				std::cout <<"<interrupted>.\n";
				break;
			}
			std::cout <<"<not interrupted>.\n";
			service->run_one();
		}
	}

public:
	TCPServer(boost::asio::ip::basic_endpoint<boost::asio::ip::tcp> endpoint, int bufferLength = 1024) {
		service = new boost::asio::io_service();
		acceptor = new boost::asio::ip::tcp::acceptor(*service);
		serviceThread = NULL;
		newSocket = NULL;

		if (bufferLength < 1)
			bufferLength = 1;
		length = bufferLength;
		data = new char[length];

		acceptor->open(endpoint.protocol());
		acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
		acceptor->bind(endpoint);
		acceptor->listen();
		if (acceptor->is_open())
		{
			newSocket = new boost::asio::ip::tcp::socket(acceptor->io_service());
			acceptor->async_accept(*newSocket, boost::bind(&TCPServer::acceptHandler, this, boost::asio::placeholders::error));
		}
	}
	virtual ~TCPServer()
	{
		acceptor->cancel();
		delete acceptor;

		service->stop();
		delete service;

		delete[] data;

		if (newSocket != NULL)
			delete newSocket;
		for (unsigned int i = 0; i < sockets.size(); i++)
			if (sockets[i] != NULL)
				delete sockets[i];
		sockets.clear();
	}

	bool run() {
		if (serviceThread == NULL) {
			try {
				serviceThread = new boost::thread(boost::bind(&TCPServer::runHandler, this));
				return true;
			} catch (std::exception &e) {
				delete serviceThread;
				serviceThread = NULL;
				return false;
			}
		}
		return true;
	}

	void stop() {
		std::cout <<"<join>.\n";
		serviceThread->interrupt();
		service->stop();
		serviceThread->join();
		std::cout <<"<joined>.\n";
		delete serviceThread;
		serviceThread = NULL;
	}

	bool isConnected() const {return acceptor->is_open();}

	int getBufferLength() {return length;}
	void setBufferLength(int newLength) {
		if (newLength < 1)
			newLength = 1;
		length = newLength;
		delete[] data;
		data = new char[length];
	}

	std::vector<boost::asio::ip::tcp::socket*> &getClients() {return sockets;}
	boost::asio::ip::tcp::socket* getClient(int index) {return (index<0 || index>=(int)sockets.size())? NULL: sockets[index];}

	/// Send a message to a specified client.
	void send(boost::asio::ip::tcp::socket* client, char* mess, int mlength) {
		if (client != NULL && client->is_open()) {
			client->async_send(boost::asio::buffer(mess, mlength), boost::bind(&TCPServer::sendHandler, this, boost::asio::placeholders::error, client, boost::asio::placeholders::bytes_transferred));
		}
	}

	/// Returns the next received message (NULL if there isn't any).
	NetworkMessage* receive() {return messages.next();}
};

}} // namespace

#endif
